//package com.ajaxjs.util.config2;
//
//import java.lang.reflect.Field;
//import java.lang.reflect.Modifier;
//import java.util.HashMap;
//import java.util.List;
//import java.util.Map;
//
//import org.springframework.beans.BeanUtils;
//import org.springframework.beans.TypeConverter;
//import org.springframework.beans.factory.BeanFactory;
//import org.springframework.beans.factory.config.BeanExpressionContext;
//import org.springframework.beans.factory.config.BeanExpressionResolver;
//import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
//import org.springframework.context.ApplicationContext;
//import org.springframework.context.ApplicationEventPublisher;
//import org.springframework.context.event.EventListener;
//import org.springframework.core.Ordered;
//import org.springframework.core.annotation.Order;
//import org.springframework.util.StringUtils;
//
//import com.ajaxjs.util.logger.LogHelper;
//import static com.ajaxjs.util.config2.ConfigurationUtils.*;
//import static com.ajaxjs.util.config2.DynamicConfigBeanPostProcessor.DYNAMIC_FIELD_BINDER_MAP;
//
//public class ConfigurationChangedEventHandler {
//	private static final LogHelper LOGGER = LogHelper.getLog(ConfigurationChangedEventHandler.class);
//
//	private static final String DOT_SYMBOL = ".";
//	private static final String INDEXED_PROP_PATTERN = "\\[\\d{1,3}]";
//
//	private final BeanExpressionResolver exprResolver;
//	private final BeanExpressionContext exprContext;
//	private final ConfigurationPropertiesBindingPostProcessor processor;
//	private final ConfigurableListableBeanFactory beanFactory;
//
//	ConfigurationChangedEventHandler(ApplicationContext applicationContext, BeanFactory beanFactory, ApplicationEventPublisher eventPublisher) {
//		if (!(beanFactory instanceof ConfigurableListableBeanFactory)) {
//			throw new IllegalArgumentException("DynamicConfig requires a ConfigurableListableBeanFactory");
//		}
//
//		ConfigurableListableBeanFactory factory = (ConfigurableListableBeanFactory) beanFactory;
//		this.beanFactory = factory;
//		this.processor = applicationContext.getBean(ConfigurationPropertiesBindingPostProcessor.class);
//		this.exprResolver = (factory).getBeanExpressionResolver();
//		this.exprContext = new BeanExpressionContext(factory, null);
//	}
//
//	/**
//	 * Listen config changed event, to process related beans and set latest values
//	 * for their fields
//	 *
//	 * @param event ConfigurationChangedEvent indicates a configuration file changed
//	 *              event
//	 */
//	@Order(Ordered.HIGHEST_PRECEDENCE)
//	@EventListener(ConfigurationChangedEvent.class)
//	public synchronized void handleEvent(ConfigurationChangedEvent event) {
//		try {
//			Map<String, Object> diff = event.getDiff();
//			Map<String, ValueBeanFieldBinder> toRefreshProps = new HashMap<>(4);
//
//			for (Map.Entry<String, Object> entry : diff.entrySet()) {
//				String key = entry.getKey();
//				processConfigPropsClass(toRefreshProps, key);
//				processValueField(key, entry.getValue());
//			}
//
//			rebindRelatedConfigurationPropsBeans(diff, toRefreshProps);
//			LOGGER.info("config changes of {} have been processed", event.getSource());
//		} catch (Exception ex) {
//			LOGGER.info("config changes of {} can not be processed, error:", event.getSource(), ex);
//		}
//	}
//
//	private void processConfigPropsClass(Map<String, ValueBeanFieldBinder> result, String key) {
//		DynamicConfigBeanPostProcessor.DYNAMIC_CONFIG_PROPS_BINDER_MAP.forEach((prefix, binder) -> {
//			if (StringUtils.startsWithIgnoreCase(normalizePropKey(key), prefix)) {
//				LOGGER.info("prefix matched for ConfigurationProperties bean: {}, prefix: {}", binder.getBeanName(), prefix);
//				result.put(binder.getBeanName(), binder);
//			}
//		});
//	}
//
//	private void processValueField(String keyRaw, Object val) throws IllegalAccessException {
//		String key = normalizePropKey(keyRaw);
//
//		if (!DYNAMIC_FIELD_BINDER_MAP.containsKey(key)) {
//			LOGGER.info("no bound field of changed property found, skip dynamic config processing of key: {}", keyRaw);
//			return;
//		}
//
//		List<ValueBeanFieldBinder> valueFieldBinders = DYNAMIC_FIELD_BINDER_MAP.get(key);
//
//		for (ValueBeanFieldBinder binder : valueFieldBinders) {
//			Object bean = binder.getBeanRef().get();
//			if (bean == null)
//				continue;
//
//			convertAndBindFieldValue(val, binder, bean);
//		}
//	}
//
//	private void convertAndBindFieldValue(Object val, ValueBeanFieldBinder binder, Object bean) throws IllegalAccessException {
//		Field field = binder.getDynamicField();
//		field.setAccessible(true);
//		String expr = binder.getExpr();
//		String newExpr = beanFactory.resolveEmbeddedValue(expr);
//
//		if (expr.startsWith(SP_EL_PREFIX)) {
//			Object evaluatedVal = exprResolver.evaluate(newExpr, exprContext);
//			field.set(bean, convertIfNecessary(field, evaluatedVal));
//		} else
//			field.set(bean, convertIfNecessary(field, val));
//
//		LOGGER.info("dynamic config found, set field: '{}' of class: '{}' with new value", field.getName(), bean.getClass().getSimpleName());
//	}
//
//	private void rebindRelatedConfigurationPropsBeans(Map<String, Object> diff, Map<String, ValueBeanFieldBinder> toRefreshProps)
//			throws IllegalAccessException {
//		for (Map.Entry<String, ValueBeanFieldBinder> entry : toRefreshProps.entrySet()) {
//			String beanName = entry.getKey();
//			ValueBeanFieldBinder binder = entry.getValue();
//			Object bean = binder.getBeanRef().get();
//
//			if (bean != null) {
//				processor.postProcessBeforeInitialization(bean, beanName);
//				// AggregateBinder - MapBinder will merge properties while binding
//				// need to check deleted keys and remove from map fields
//				removeMissingPropsMapFields(diff, bean, binder.getExpr());
//				LOGGER.info("changes detected, re-bind ConfigurationProperties bean: {}", beanName);
//			}
//		}
//	}
//
//	private void removeMissingPropsMapFields(Map<String, Object> diff, Object rootBean, String prefix) throws IllegalAccessException {
//		for (Map.Entry<String, Object> entry : diff.entrySet()) {
//			Object value = entry.getValue();
//
//			if (value != null)
//				// only null value prop need to be removed from field value
//				continue;
//
//			String rawKey = entry.getKey();
//			// 'a.b[1].c.d' liked changes would be refreshed wholly, no need to handle
//			if (rawKey.matches(INDEXED_PROP_PATTERN))
//				continue;
//
//			// if key 'a.b.c.d' is removed, need to check if 'a.b.c' is a map, if so, remove
//			// map key 'd'
//			String normalizedFieldPath = findParentPath(prefix, rawKey);
//			String leafKey = rawKey.substring(rawKey.lastIndexOf(DOT_SYMBOL) + 1);
//			removeMissingMapKeyIfMatch(getTargetClassOfBean(rootBean), rootBean, normalizedFieldPath, leafKey);
//		}
//	}
//
//	private void removeMissingMapKeyIfMatch(Class<?> clazz, Object obj, String path, String mapKey) throws IllegalAccessException {
//		int pos = path.indexOf(DOT_SYMBOL);
//		boolean onLeaf = pos == -1;
//		Field[] fields = clazz.getDeclaredFields();
//
//		for (Field f : fields) {
//			if (isIgnorableField(f))
//				continue;
//
//			String fieldName = f.getName();
//			boolean matchObjPath = StringUtils.startsWithIgnoreCase(path, normalizePropKey(fieldName));
//
//			if (matchObjPath && onLeaf && Map.class.isAssignableFrom(f.getType())) {
//				f.setAccessible(true);
//				((Map<?, ?>) f.get(obj)).remove(mapKey);
//				LOGGER.info("key {} has been removed from {} because of configuration change.", mapKey, path);
//				break;
//			}
//
//			// dive to next level for case: path: a.b.c, field: b
//			if (matchObjPath && !onLeaf) {
//				f.setAccessible(true);
//				Object subObj = f.get(obj);
//				removeMissingMapKeyIfMatch(subObj.getClass(), subObj, path.substring(pos + 1), mapKey);
//			}
//		}
//	}
//
//	private boolean isIgnorableField(Field f) {
//		int modifiers = f.getModifiers();
//		Class<?> type = f.getType();
//		return Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers) || BeanUtils.isSimpleValueType(type);
//	}
//
//	private String findParentPath(String prefix, String rawKey) {
//		String normalizedFieldPath = normalizePropKey(rawKey).substring(prefix.length() + 1);
//		int pathPos = normalizedFieldPath.lastIndexOf(DOT_SYMBOL);
//		if (pathPos != -1)
//			normalizedFieldPath = normalizedFieldPath.substring(0, pathPos);
//		else
//			normalizedFieldPath = "";
//
//		return normalizedFieldPath;
//	}
//
//	private Object convertIfNecessary(Field field, Object value) {
//		TypeConverter converter = beanFactory.getTypeConverter();
//		return converter.convertIfNecessary(value, field.getType(), field);
//	}
//}